package controller

import (
	"context"
	v1 "k8s.io/api/apps/v1"
	v13 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/dynamic/dynamicinformer"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"operator/pkg/types/mesh"
	"operator/pkg/types/unstruct"
	"sync"
)

type DeploymentController struct {
	client      dynamic.Interface
	ctx         context.Context
	mu          sync.RWMutex
	cfg         *rest.Config
	workerCount int
	maxRetry    int
	informer    dynamicinformer.DynamicSharedInformerFactory
	queue       workqueue.RateLimitingInterface
	mapper      *restmapper.DeferredDiscoveryRESTMapper
	dLister     cache.GenericLister
	updateCh    chan *v1.Deployment
}

func (m *DeploymentController) updateChan(obj interface{}) {
	switch obj.(type) {
	case *v1.Deployment:
		m.updateCh <- obj.(*v1.Deployment)
	case *unstructured.Unstructured:
		deployment := &v1.Deployment{}
		err := unstruct.UnstructToObject(obj.(*unstructured.Unstructured), deployment)
		if err != nil {
			return
		}
		m.updateCh <- deployment
	}
}

func (m *DeploymentController) getGVR(gvk schema.GroupVersionKind) *schema.GroupVersionResource {
	restmapper, err := m.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil
	}

	return &restmapper.Resource
}

func (m *DeploymentController) getGVRFromObject(obj runtime.Object) *schema.GroupVersionResource {
	gvk := schema.GroupVersionKind{}
	switch obj.(type) {
	case *mesh.Mesh:
		gvk.Version = "v1"
		gvk.Group = "king.io"
		gvk.Kind = "mesh"
	case *v13.Pod:
		gvk.Version = "v1"
		gvk.Kind = "pod"
	case *v1.Deployment:
		gvk.Version = "v1"
		gvk.Group = "apps"
		gvk.Kind = "deployment"
	}

	restmapper, err := m.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil
	}

	return &restmapper.Resource
}

func NewDeploymentController(ctx context.Context,
	config *rest.Config,
	dSharedInformer dynamicinformer.DynamicSharedInformerFactory,
	mapper *restmapper.DeferredDiscoveryRESTMapper,
	client dynamic.Interface,
	workerCount int,
	maxRetry int,
	ch chan *v1.Deployment,
) *DeploymentController {
	c := &DeploymentController{
		ctx:         ctx,
		cfg:         config,
		client:      client,
		maxRetry:    maxRetry,
		workerCount: workerCount,
		informer:    dSharedInformer,
		mapper:      mapper,
		queue:       workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "podController"),
		mu:          sync.RWMutex{},
		updateCh:    ch,
	}

	dSharedInformer.ForResource(*c.getGVRFromObject(&v13.Pod{})).Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: c.updateChan,
		UpdateFunc: func(oldObj, newObj interface{}) {
			c.updateChan(newObj)
		},
		DeleteFunc: c.updateChan,
	})

	c.dLister = dSharedInformer.ForResource(*c.getGVRFromObject(&v1.Deployment{})).Lister()

	return c
}
